package keqiang.com.kq_flutter_core_widget.channel;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;

import com.alibaba.sdk.android.oss.callback.OSSProgressCallback;
import com.alibaba.sdk.android.oss.internal.OSSAsyncTask;
import com.alibaba.sdk.android.oss.model.*;
import com.keqiang.base.GlobalParamUtils;
import com.keqiang.base.IOUtils;
import com.keqiang.base.mime.MimeUtils;
import com.keqiang.base.oss.OSS;
import com.keqiang.base.oss.OSScaleType;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodCodec;
import keqiang.com.kq_flutter_core_widget.interf.IKqRoute;
import keqiang.com.kq_flutter_core_widget.oss.FlutterOssMultiPICUpload;
import keqiang.com.kq_flutter_core_widget.BuildConfig;
import me.zhouzhuo810.magpiex.utils.FileUtil;
import me.zhouzhuo810.magpiex.utils.StrUtil;

/**
 * 原生OSS交互
 *
 * @author zhouzhuo810
 * @date 2023/2/20 19:31
 */
public class NativeOSSChannel extends MethodChannel {
    
    private static final int INVALID_VALUE = -1;
    
    // 通道id
    private static final String CHANNEL = "flutter.keqiang.com/oss";
    
    // 下载文件
    private static final String METHOD_DOWNLOAD = "ossDownload";
    // 获取缓存文件
    private static final String METHOD_GET_OSS_CACHE_FILE = "getOssCacheFile";
    // 下载图片
    private static final String METHOD_DOWNLOAD_IMAGE = "ossDownloadImage";
    // 取消下载
    private static final String METHOD_CANCEL = "ossCancelDownload";
    // 删除OSS文件
    private static final String METHOD_DELETE = "ossDelete";
    // 批量上传OSS图片
    private static final String METHOD_UPLOAD_MULTI_PIC = "ossUploadPics";
    // 单个上传OSS文件
    private static final String METHOD_UPLOAD_FILE = "ossUploadFile";
    
    private static final String METHOD_INIT_OSS = "initOSS";
    
    
    private Handler mHandler;
    
    private Context mContext;
    
    public void setContext(Context context) {
        mContext = context;
    }
    
    /**
     * 记录下载任务
     */
    private final ConcurrentHashMap<String, OSSAsyncTask<?>> mTaskMap = new ConcurrentHashMap<>();
    
    public NativeOSSChannel(@NonNull BinaryMessenger messenger) {
        super(messenger, CHANNEL);
        
        init();
    }
    
    public NativeOSSChannel(@NonNull BinaryMessenger messenger, @NonNull String name) {
        super(messenger, name);
        
        init();
    }
    
    public NativeOSSChannel(@NonNull BinaryMessenger messenger, @NonNull String name, @NonNull MethodCodec codec) {
        super(messenger, name, codec);
        
        init();
    }
    
    public NativeOSSChannel(@NonNull BinaryMessenger messenger, @NonNull String name, @NonNull MethodCodec codec, @Nullable BinaryMessenger.TaskQueue taskQueue) {
        super(messenger, name, codec, taskQueue);
        
        init();
    }
    
    private void init() {
        setMethodCallHandler((call, result) -> {
            switch (call.method) {
                case METHOD_DOWNLOAD:
                    //OSS 下载
                    String ossKey = call.argument("ossKey");
                    GetObjectResult ossResult = OSS.downLoad(ossKey);
                    Map<String, Object> fileInfo = saveFile(ossResult, null);
                    Map<String, Object> resultMap = new HashMap<>();
                    resultMap.put("code", fileInfo == null ? 0 : 1);
                    resultMap.put("msg", fileInfo == null ? "下载失败" : "下载成功");
                    resultMap.put("data", fileInfo);
                    result.success(resultMap);
                    break;
                case METHOD_DOWNLOAD_IMAGE:
                    //OSS 下载图片
                    ossKey = call.argument("ossKey");
                    Double width = call.<Double>argument("width");
                    Double height = call.<Double>argument("height");
                    Double quality = call.<Double>argument("quality");
                    String scaleType = call.argument("scaleType");
                    String tag = call.argument("tag");
                    asyncDownloadImage(ossKey, width == null ? INVALID_VALUE : width, height == null ? INVALID_VALUE : height, quality == null ? INVALID_VALUE : quality, scaleType, tag, result);
                    break;
                case METHOD_GET_OSS_CACHE_FILE:
                    //OSS 获取缓存
                    ossKey = call.argument("ossKey");
                    width = call.<Double>argument("width");
                    height = call.<Double>argument("height");
                    getCacheFile(ossKey, width == null ? INVALID_VALUE : width, height == null ? INVALID_VALUE : height, result);
                    break;
                case METHOD_CANCEL:
                    //OSS 取消下载
                    tag = call.argument("tag");
                    cancel(tag);
                    resultMap = new HashMap<>();
                    resultMap.put("code", 1);
                    resultMap.put("msg", "取消成功");
                    result.success(resultMap);
                    break;
                case METHOD_DELETE:
                    //OSS 删除文件
                    ossKey = call.argument("ossKey");
                    asyncDelete(ossKey, result);
                    break;
                case METHOD_UPLOAD_MULTI_PIC:
                    //OSS 批量上传图片
                    List<Map<String, Object>> ossKeyAndPaths = call.argument("ossKeyAndPaths");
                    FlutterOssMultiPICUpload multiPICUpload = new FlutterOssMultiPICUpload();
                    multiPICUpload.async(result :: success, ossKeyAndPaths);
                    break;
                case METHOD_UPLOAD_FILE:
                    //OSS 上传图片
                    ossKey = call.argument("ossKey");
                    String filePath = call.argument("filePath");
                    String mime = call.argument("mime");
                    tag = call.argument("tag");
                    asyncUploadFile(ossKey, filePath, mime, tag, result);
                    break;
                case METHOD_INIT_OSS:
                    String bucketName = call.argument("bucketName");
                    result.success(null);
                    if (mContext != null) {
                        if (mContext instanceof IKqRoute) {
                            ((IKqRoute) mContext).initOSS(bucketName);
                        }
                    }
                    break;
            }
        });
    }
    
    /**
     * 获取缓存路径
     *
     * @param ossKey ossKey
     * @param width  宽度
     * @param height 高度
     * @param result flutter结果
     */
    private void getCacheFile(String ossKey, double width, double height, Result result) {
        if (ossKey != null && !ossKey.isEmpty()) {
            final String cacheFileName = "w_" + (width != INVALID_VALUE ? (int) width : 4096) + "_h_" + (height != INVALID_VALUE ? (int) height : 4096) + "_" + ossKey.replace("/", "_");
            String cacheDirPath = GlobalParamUtils.getCacheDirPath();
            
            FileUtil.mkDirs(cacheDirPath);
            
            File file = new File(cacheDirPath, cacheFileName);
            if (file.exists()) {
                Map<String, Object> fileInfo = new HashMap<>();
                fileInfo.put("fileSize", file.length());
                fileInfo.put("fileName", file.getName());
                fileInfo.put("filePath", file.getAbsolutePath());
                
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", 1);
                resultMap.put("msg", "ok");
                resultMap.put("data", fileInfo);
                result.success(resultMap);
                
                if (BuildConfig.DEBUG) {
                    Log.d("OSS", "OSS 获取缓存成功, ossKey= " + ossKey + ", fileInfo=" + fileInfo);
                }
                return;
            }
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", 0);
        resultMap.put("msg", ossKey + "没有缓存");
        resultMap.put("data", null);
        result.success(resultMap);
    }
    
    /**
     * oss上传文件
     */
    public void asyncUploadFile(String ossKey, String filePath, String mime, String tag, MethodChannel.Result result) {
        Map<String, Object> resultMap;
        if (StrUtil.isEmpty(filePath)) {
            resultMap = new HashMap<>();
            resultMap.put("code", 0);
            resultMap.put("msg", "上传失败");
            result.success(resultMap);
            if (BuildConfig.DEBUG) {
                Log.e("OSS", "OSS 上传失败：filePath不能为空");
            }
            return;
        }
        File file = new File(filePath);
        if (!file.exists()) {
            resultMap = new HashMap<>();
            resultMap.put("code", 0);
            resultMap.put("msg", "上传失败");
            result.success(resultMap);
            if (BuildConfig.DEBUG) {
                Log.e("OSS", "OSS 上传失败：文件不存在，filePath=" + filePath);
            }
            return;
        }
        @SuppressLint("NotifyDataSetChanged")
        OSSProgressCallback<PutObjectRequest> progressCallback = StrUtil.isEmpty(tag) ? null : (request, currentSize, totalSize) -> {
            if (mNativeOSSProgressHandler != null) {
                // 这里发送上传进度
                HashMap<String, Object> map = new HashMap<>();
                map.put("tag", tag);
                map.put("currentSize", currentSize);
                map.put("totalSize", totalSize);
                // event回调必须放到主线程
                if (mHandler == null) {
                    mHandler = new Handler(Looper.getMainLooper());
                }
                mHandler.post(() -> {
                    EventChannel.EventSink eventSink = mNativeOSSProgressHandler.getEventSink();
                    if (eventSink != null) {
                        eventSink.success(map);
                    }
                });
            }
        };
        @SuppressLint("NotifyDataSetChanged")
        OSS.UploadCompletedCallback uploadCompletedCallback
            = new OSS.UploadCompletedCallback() {
            @Override
            public void onSuccess(@NonNull PutObjectResult result1) {
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", 1);
                resultMap.put("msg", "上传成功");
                result.success(resultMap);
                if (BuildConfig.DEBUG) {
                    Log.e("OSS", "OSS 上传成功，ossKey=" + ossKey + ", filePath=" + filePath);
                }
            }
            
            @Override
            public void onFailure(Exception e) {
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", 0);
                resultMap.put("msg", "上传失败");
                result.success(resultMap);
                if (BuildConfig.DEBUG) {
                    Log.e("OSS", "OSS 上传失败，ossKey=" + ossKey + ", filePath=" + filePath);
                }
            }
        };
        
        ObjectMetadata metadata = new ObjectMetadata();
        if (!TextUtils.isEmpty(mime)) {
            metadata.setContentType(mime);
        }
        OSS.asyncUpload(ossKey, filePath, metadata,
            progressCallback, uploadCompletedCallback);
    }
    
    /**
     * 异步下载图片
     *
     * @param width  单位dp
     * @param height 单位dp
     * @param tag    用于监听下载进度或取消下载
     * @param result 异步下载结果回调
     */
    public void asyncDownloadImage(@Nullable String ossKey, double width, double height, double quality,
                                   @Nullable String scaleType, @Nullable String tag, @NonNull MethodChannel.Result result) {
        if (ossKey == null || ossKey.isEmpty()) {
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put("code", 0);
            resultMap.put("msg", "下载失败：ossKey为空");
            resultMap.put("data", null);
            result.success(resultMap);
            return;
        }
        String xOssProcess = "image/resize,";
        if (width != INVALID_VALUE && height != INVALID_VALUE) {
            xOssProcess += "w_" + ((int) width) + ",h_" + ((int) height);
        } else {
            xOssProcess += "w_4096,h_4096";
        }
        
        String scale = mapScaleType(scaleType);
        if (!TextUtils.isEmpty(scale)) {
            xOssProcess += ",m_" + scale;
        }
        
        if (quality != INVALID_VALUE) {
            xOssProcess += "/quality,Q_" + ((int) quality);
        }
        
        if (BuildConfig.DEBUG) {
            Log.d("OSS", "OSS 开始下载，ossKey=" + ossKey);
        }
        
        final String cacheFileName = "w_" + (width != INVALID_VALUE ? (int) width : 4096) + "_h_" + (height != INVALID_VALUE ? (int) height : 4096) + "_" + ossKey.replace("/", "_");
        
        OSSAsyncTask<?> ossAsyncTask = OSS.asyncDownLoad(ossKey, xOssProcess, null, new OSS.DownLoadCompletedCallback() {
            @Override
            public void onSuccess(@NonNull GetObjectResult getObjectResult) {
                if (tag != null) {
                    mTaskMap.remove(tag);
                }
                
                Map<String, Object> fileInfo = saveFile(getObjectResult, cacheFileName);
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", fileInfo == null ? 0 : 1);
                resultMap.put("msg", fileInfo == null ? "下载失败" : "下载成功");
                resultMap.put("data", fileInfo);
                result.success(resultMap);
                if (BuildConfig.DEBUG) {
                    Log.d("OSS", "OSS 下载成功, ossKey= " + ossKey + ", fileInfo=" + fileInfo);
                }
            }
            
            @Override
            public void onFailure(@Nullable Exception e) {
                if (tag != null) {
                    mTaskMap.remove(tag);
                }
                if (BuildConfig.DEBUG) {
                    Log.e("OSS", "OSS 下载失败，ossKey=" + ossKey + ", e=" + e);
                    if (e != null) {
                        e.printStackTrace();
                    }
                }
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", 0);
                resultMap.put("msg", "下载失败：" + (e == null ? null : e.getLocalizedMessage()));
                resultMap.put("data", null);
                result.success(resultMap);
            }
        });
        
        if (tag != null) {
            mTaskMap.put(tag, ossAsyncTask);
        }
    }
    
    
    /**
     * 异步删除数据
     *
     * @param result 删除结果回调
     */
    public void asyncDelete(@Nullable String ossKey, @NonNull MethodChannel.Result result) {
        OSS.asyncDelete(ossKey, new OSS.DeleteCompletedCallback() {
            @Override
            public void onSuccess(@NonNull DeleteObjectResult deleteObjectResult) {
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", 1);
                resultMap.put("msg", null);
                result.success(resultMap);
                if (BuildConfig.DEBUG) {
                    Log.d("OSS", "OSS 删除成功, ossKey=" + ossKey);
                }
            }
            
            @Override
            public void onFailure(@Nullable Exception e) {
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("code", 0);
                resultMap.put("msg", "删除失败：" + (e == null ? "" : e.getLocalizedMessage()));
                result.success(resultMap);
                if (BuildConfig.DEBUG) {
                    Log.e("OSS", "OSS 删除失败, ossKey=" + ossKey);
                    if (e != null) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
    
    /**
     * 取消OSS任务
     */
    public void cancel(@Nullable String tag) {
        if (tag == null) {
            return;
        }
        if (BuildConfig.DEBUG) {
            Log.d("OSS", "OSS 取消下载, tag=" + tag);
        }
        OSSAsyncTask<?> task = mTaskMap.get(tag);
        if (task != null && !task.isCompleted() && !task.isCompleted()) {
            task.cancel();
        }
        mTaskMap.remove(tag);
    }
    
    /**
     * 将RN图片缩放类型转化为OSS图片缩放类型
     */
    private String mapScaleType(String scaleType) {
        if (TextUtils.isEmpty(scaleType)) {
            return null;
        }
        
        if ("cover".equals(scaleType)) {
            return OSScaleType.MFIT;
        } else if ("fill".equals(scaleType)) {
            return OSScaleType.FIXED;
        } else if ("scaleDown".equals(scaleType) || "contain".equals(scaleType)) {
            return OSScaleType.LFIT;
        }
        
        return null;
    }
    
    private Map<String, Object> saveFile(@Nullable GetObjectResult result, String fileName) {
        if (result == null) {
            return null;
        }
        
        InputStream inputStream = result.getObjectContent();
        if (inputStream == null) {
            return null;
        }
        
        ObjectMetadata metadata = result.getMetadata();
        String contentType = metadata == null ? null : metadata.getContentType();
        String fileSuffix = MimeUtils.getExtensionFromMimeType(contentType);
        if (fileSuffix != null && fileSuffix.equalsIgnoreCase("gif")) {
            //gif图片直接屏蔽，因为加载gif一体机会出现自动重启情况。
            if (BuildConfig.DEBUG) {
                Log.e("OSS", "Gif 图片直接忽略");
            }
            return null;
        }
        
        if (fileName == null) {
            fileName = UUID.randomUUID().toString() + (TextUtils.isEmpty(fileSuffix) ? "" : "." + fileSuffix);
        }
        
        String cacheDirPath = GlobalParamUtils.getCacheDirPath();
        
        FileUtil.mkDirs(cacheDirPath);
        
        File file = new File(cacheDirPath, fileName);
        if (file.exists()) {
            //noinspection ResultOfMethodCallIgnored
            file.delete();
        }
        
        byte[] buffer = new byte[2048];
        int readCount;
        FileOutputStream fout = null;
        try {
            fout = new FileOutputStream(file);
            while ((readCount = inputStream.read(buffer)) != -1) {
                fout.write(buffer, 0, readCount);
            }
            fout.flush();
            Map<String, Object> fileInfo = new HashMap<>();
            fileInfo.put("fileSize", file.length());
            fileInfo.put("fileName", file.getName());
            fileInfo.put("filePath", file.getAbsolutePath());
            return fileInfo;
        } catch (Exception e) {
            if (BuildConfig.DEBUG) {
                Log.e("OSS", "OSS 下载文件保存失败");
                e.printStackTrace();
            }
            return null;
        } finally {
            IOUtils.closeQuietly(inputStream);
            IOUtils.closeQuietly(fout);
        }
    }
    
    private NativeOSSProgressHandler mNativeOSSProgressHandler;
    
    public void setProgressHandler(NativeOSSProgressHandler nativeOSSProgressHandler) {
        this.mNativeOSSProgressHandler = nativeOSSProgressHandler;
    }
}
